package com.umpay.util;

import com.bs.mpsp.util.SignUtil;
import com.umpay.business.common.MgrCommonBusiness;
import com.umpay.dict.DataDict;
import com.umpay.dict.XmlData;
import com.umpay.dict.retcode.MerAccessEC;
import com.umpay.dto.memberMgr.res.MerInfRes;
import com.umpay.exception.BusinessException;
import com.umpay.log.mpsp.MpspLogger;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 签名认证类
 * 
 * @ClassName SignatureAuth
 * @Description TODO
 * @author FENG JUN LIU
 * @Date 2018年1月15日
 * @version 1.0.0
 */
@Component
public class SignatureAuthUtil {

    public static final Logger log = LogManager.getLogger(SignatureAuthUtil.class);
	@Autowired
	private MgrCommonBusiness mgrCommonBusinessImpl;
	
    /**
     * 签名认证
     * 
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param sign
     * @param plain
     * @param merId
     * @throws BusinessException
     */
    public void verifyMerSign(String sign, String plain, String merId) throws BusinessException {
        try {
            byte[] certFile = getCertFile(merId);
            X509Certificate cert = SignUtil.genCertificate(certFile);
            byte[] signData = Base64Util.decode(sign.getBytes("utf-8"));
            boolean result = SignUtil.verify(cert, plain.getBytes("utf-8"), signData);
            if (!result) {
                log.info("【商户签名验证】使用gbk验签失败，尝试使用utf-8验签");
                result = SignUtil.verify(cert, plain.getBytes("gbk"), signData);
            }
            if (result) {
                log.info("【商户签名验证】验证商户签名信息成功");
            } else {
                log.error(String.format("【商户签名验证】验签失败,plain=[%s];sign=[%s]", plain, sign));
                throw new BusinessException(MerAccessEC.MERSIGN_ERR, "验证商户签名失败");
            }
        } catch (BusinessException e) {
            log.error(String.format("【商户签名验证】验签失败,retCode=[%s];retMsg=[%s]", e.getCode(), e.getMessage()));
            throw new BusinessException(e.getCode(), e.getMessage());
        }catch (Exception e) {
            log.error(String.format("【商户签名验证】验签失败,plain=[%s];sign=[%s]", plain, sign), e);
            throw new BusinessException(MerAccessEC.MERSIGN_ERR, "验证商户签名失败");
        }

    }
    
    /**
     * 针对data为String类型，验签并返回报文
     * @param sign
     * @param plain
     * @param merId
     * @return
     * @throws BusinessException
     */
    public Map<String,String> verifyMerSignRetMessage(String sign, String plain, String merId) throws BusinessException {
    	Map<String,String> resMap = new HashMap<String,String>();
        try {
        	MerInfRes merInf = null;
        	merInf = getMerInfByMerId(merId);
			boolean result = vertifyMerSign(merInf,sign,plain,merId);
            resMap.put("accessType", StringUtil.trim(merInf.getAccessType()));
            resMap.put("accessName", merInf.getAccessName());
            resMap.put("depBankMerId", merInf.getDepBankMerId());
            if (result) {
                log.info("【商户签名验证】验证商户签名信息成功");
            } else {
                log.error(String.format("【商户签名验证】验签失败,plain=[%s];sign=[%s]", plain, sign));
                throw new BusinessException(MerAccessEC.MERSIGN_ERR, "验证商户签名失败");
            }
        } catch (BusinessException e) {
            log.error(String.format("【商户签名验证】验签失败,retCode=[%s];retMsg=[%s]", e.getCode(), e.getMessage()));
            throw new BusinessException(e.getCode(), e.getMessage());
        }catch (Exception e) {
            log.error(String.format("【商户签名验证】验签失败,plain=[%s];sign=[%s]", plain, sign), e);
            throw new BusinessException(MerAccessEC.MERSIGN_ERR, "验证商户签名失败");
        }
        
        return resMap;
    }

    /**
     * 签名认证
     * 
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param sign
     * @param data
     * @param merId
     * @throws BusinessException
     */
    public void verifyMerSign(String sign, Map<String, String> data, String merId) throws BusinessException {
        verifyMerSign(sign, getPlain(data), merId);
    }
    
    /**
     * data为map类型，验签并返回报文
     * @param sign
     * @param data
     * @param merId
     * @return
     * @throws BusinessException
     */
    public Map<String,String> verifyMerSignRetMessage(String sign, Map<String, String> data, String merId) throws BusinessException {
        return verifyMerSignRetMessage(sign, getPlain(data), merId);
    }

    /**
     * 生成代簽名数据
     * 
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param reqMap
     * @return
     */
    public String getPlain(Map<String, String> reqMap) {
        Object[] obj = reqMap.keySet().toArray();
        String s, value = "";
        StringBuilder reqstr = new StringBuilder("");
        Arrays.sort(obj);
        for (Object object : obj) {
            s = object.toString();
            if (!s.equalsIgnoreCase(XmlData.PLAIN) && !XmlData.SIGN.equals(s)) { // 商户请求的参数如果为空，不传递给后台，并且sign和plain字段不参与数据组装给后台
                value = StringUtils.trim(reqMap.get(s)); // 获得非空请求字段值
                if (StringUtils.isNotEmpty(value)) {
                    reqstr.append(s).append("=").append(value).append("&");
                }
            }
        }
        String plain = reqstr.substring(0, reqstr.length() - 1); // 截取明文串(最后一位多个"&"符号)
        log.debug(String.format("商户请求数据组装字符串结果为：%s", plain));
        return plain;
    }

    /**
     * 生成联动签名数据(前台通知用)
     * 
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param data
     * @return
     */
    public String signMsgFront(Map<String, String> data) {
        String platKey = PropertiesUtil.getStrValue("plat_key"); // 获取密文证书
        String plain = signMsg(platKey, getPlain(data)); // 对明文串进行签名
        log.debug(String.format("明文获得的加密串是：%s", plain));
        return plain;
    }

    /**
     * 生成联动签名数据
     * 
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param data
     * @return
     */
    public String signMsg(String data) {
        String platKey = PropertiesUtil.getStrValue("plat_key"); // 获取密文证书
        String plain = signMsg(platKey, data); // 对明文串进行签名
        log.debug(String.format("明文获得的加密串是：%s", plain));
        return plain;
    }

    /**
     * 生成签名数据
     * 
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param platKey
     * @param plain
     * @return
     */
    private String signMsg(String platKey, String plain) {
        PrivateKey privateKey = getPrivateKey(platKey);
        String sign = null;
        try {
            byte[] signData = SignUtil.sign(privateKey, plain.getBytes("UTF-8"));
            sign = new String(Base64Util.encode(signData));
        } catch (Exception e) {
            log.error("签名异常", e);
            throw new BusinessException(MerAccessEC.MERSIGN_ERR, "签名异常");
        }
        return sign;
    }

    /**
     * @Description (TODO这里用一句话描述这个方法的作用)
     * @param platKey
     * @param data
     * @return
     */
    private PrivateKey getPrivateKey(String platKey) {
        InputStream in = null;
        byte[] key = null;
        PrivateKey pk = null;
        try {
            key = new byte[4096];
            in = this.getClass().getClassLoader().getResourceAsStream(platKey); // 读取证书文件
            in.read(key);
            pk = SignUtil.genPrivateKey(key);
        } catch (Exception e) {
            log.error("加载证书异常", e);
        }
        return pk;
    }
    private byte[] getCertFile1(String merId) throws Exception {
        byte[] certFile = new byte[20480];
        InputStream in_cert = null;
        try {
            in_cert = this.getClass().getClassLoader().getResourceAsStream("cert" + File.separator + "cert_2d59.crt");
            in_cert.read(certFile);
        } catch (Exception localException) {
            if (in_cert != null)
                try {
                    in_cert.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            throw new Exception(localException);
        } finally {
            if (in_cert != null) {
                try {
                    in_cert.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return certFile;
    }
    /**
     * 获取平台商户私钥
     * @param merId
     * @return
     * @throws BusinessException
     */
    private byte[] getCertFile(String merId) throws BusinessException {
    	try {
			MerInfRes merInf = getMerInfByMerId(merId);
			byte[] certFile = merInf.getMerCert();
			return certFile;
		} catch (BusinessException e) {
			log.error(String.format("查询平台商户信息发生异常，获取商户证书失败。retCode=%s,retMsg=%s",e.getCode(), e.getMessage()));
			throw e;
		} catch (Exception e) {
			log.error("获取平台商户证书发生异常"+e);
			throw new BusinessException(MerAccessEC.SYSTEM_ERR, "获取平台商户证书失败");
		}
    } 
    
    /**
     * 查询商户信息
     * @param merId
     * @return
     * @throws BusinessException
     */
    private MerInfRes getMerInfByMerId(String merId) throws BusinessException {
    	Map<String, String> reqMap = new HashMap<String, String>();
		reqMap.put(XmlData.RPID,  RpidUtils.getRpid());
		reqMap.put(XmlData.MERID,  StringUtils.trim(merId));
		MerInfRes merInf;
		try {
			merInf = mgrCommonBusinessImpl.qryMerInfo(reqMap);
			return merInf;
		} catch (BusinessException e) {
			log.error(String.format("查询平台商户信息发生异常,retCode=%s,retMsg=%s",e.getCode(), e.getMessage()));
			throw new BusinessException(e.getCode(), e.getMessage());
		} catch (Exception e) {
			log.error("查询平台商户信息发生异常");
			throw new BusinessException(MerAccessEC.SYSTEM_ERR, "查询商户信息失败");
		}
    	
    }

    /**
     * 验证商户签名
     * @param merInf
     * @param sign
     * @param plain
     * @param merId
     * @return boolean 验签结果
     */
    private boolean vertifyMerSign(MerInfRes merInf, String sign, String plain, String merId) {
        byte[] certFile = merInf.getMerCert();
        X509Certificate cert = SignUtil.genCertificate(certFile);
        byte[] signData = new byte[0];
        boolean result = false;
        try {
            signData = Base64Util.decode(sign.getBytes("utf-8"));
            result = SignUtil.verify(cert, plain.getBytes("utf-8"), signData);
            if (!result) {
                log.info("【商户签名验证】使用gbk验签失败，尝试使用utf-8验签");
                result = SignUtil.verify(cert, plain.getBytes("gbk"), signData);
            }
        } catch (UnsupportedEncodingException e) {
            log.error("验签发生编码异常"+e);
        }
        return result;
    }


    /**
     * 对商户验签失败结果进行过滤
     * 全局强校验标识，当标识为TRUE，验签失败，流程异常结束
     * 商户白名单，如果商户在白名单中，验签失败，流程结束；否则流程继续
     * 验签失败，打印告警日志，用户发现问题
     * @param merInf
     * @param sign
     * @param plain
     * @param merId
     * @return
     */
   /* private boolean filterVertifyMerSign(MerInfRes merInf, String sign, String plain, String merId) {
        String flag = PropertiesUtil.getStrValue("vertify.merSign.global.flag");
        String merIds = PropertiesUtil.getStrValue("must.vertify.merSign.merIds");
        boolean result = vertifyMerSign(merInf, sign, plain, merId);
        if (result) {
            log.info("【商户签名验证】验签成功");
            return result;
        } else {
            if (DataDict.FLAG_TRUE.equals(flag)) {
                log.info(String.format("【商户签名验证】验签失败,验签全局标识Flag=[%s],进行拦截，异常结束",flag));
                MpspLogger.printKeepLiveMpspMsg(getMpspMg(merId, MerAccessEC.MERSIGN_ERR_TEMP,"验签失败"));
                return result;
            } else {
                if (merIds.indexOf("," + merId) != -1) {
                    log.warn(String.format("【商户签名验证】验签失败，商户号merId=[%s]【在】强校白名单中whiteMerIds=[%s],进行拦截，异常结束",merId,merIds));
                    MpspLogger.printKeepLiveMpspMsg(getMpspMg(merId, MerAccessEC.MERSIGN_ERR_TEMP,"验签失败"));
                    return result;
                }else {
                    log.warn(String.format("【商户签名验证】验签失败，商户号merId=[%s]【不在】强校白名单中whiteMerIds=[%s],流程继续",merId,merIds));
                    MpspLogger.printKeepLiveMpspMsg(getMpspMg(merId, MerAccessEC.MERSIGN_ERR_TEMP,"验签失败"));
                    return true;
                }
            }
        }

    }*/

    /*private String getMpspMg(String merId, String retCode,String retMsg) {
        String mpspMsg = "http://b2b.umfintech.com/merAccess/merSgin/vertifySign,POST,%s,%s,%s,%s,%s,888ms";
        String orderId = String.valueOf(System.currentTimeMillis());
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");//设置日期格式
        String reqDate = df.format(new Date());// new Date()为获取当前系统时间
        return String.format(mpspMsg,merId,orderId,reqDate,retCode,retMsg);

    }*/


//    public static void main(String[] args) {
//        System.out.println( new SignatureAuthUtil().getMpspMg("80001100","0000","交易成功"));
//    }
}
